// GNU (L)GPLv3+ here.

#include "MaxsiGameEngine.h"

extern bool ExitedGameLoop;
extern bool ShouldMainLoop;
extern MaxsiHandle ShouldRenderEvent;

BeginMaxsiNamespace

float	LogicGameTime;
float	LogicLastGameTime;
float	LogicLastFPSGameTime	=	0.0;
float	LogicDeltaTime;
float	LogicFPS				=	0.0;
size_t	LogicFramesPerFPS		=	30;
size_t	LogicFrameNum			=	0;

float	RenderGameTime;
float	RenderLastGameTime;
float	RenderLastFPSGameTime	=	0.0;
float	RenderDeltaTime;
float	RenderFPS				=	0.0;
size_t	RenderFramesPerFPS		=	30;
size_t	RenderFrameNum			=	0;

Time_t LogicBegun;
Time_t LogicNow;
Time_t RenderBegun;
Time_t RenderNow;

#define MAXSI_NUM_ENTITIES 16384
MaxsiEntity* EntityList[MAXSI_NUM_ENTITIES];
MaxsiHandle UpdateJobStatusLock	=	NULL;

size_t EntityUpdateJobsLeft 	=	0;
size_t EntityPhysicsJobsLeft 	=	0;

float RandomFloat(float a, float b)
{
	MAXSI_TODO("This sucks. A lot.");
	return ((b-a)*((float)rand()/RAND_MAX))+a;
}

#define MAXSI_NUM_PLANETS 1

MaxsiError CreateScene()
{
	UpdateJobStatusLock		=	ThreadSystem()->CreateCriticalSection();

	if ( UpdateJobStatusLock == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	for ( size_t I = 0; I < MAXSI_NUM_ENTITIES; I++) { EntityList[I] = NULL; }

	for ( size_t I = 0; I < MAXSI_NUM_PLANETS; I++ )	
	{
		MaxsiPropPhysics* Planet = new MaxsiPropPhysics;

		Planet->Rotation = Vec3(RandomFloat(-128.0, 128.0), RandomFloat(-128.0, 128.0), RandomFloat(-128.0, 128.0));

		EntityList[I]	=	Planet;
	}

	return MAXSI_ERROR_SUCCESS;

}

MaxsiError DeleteScene()
{
	for ( size_t I = 0; I < MAXSI_NUM_ENTITIES; I++) { IFDELSINGLE(EntityList[I]); }

	return MAXSI_ERROR_SUCCESS;
}


MaxsiError CreateSceneUpdateJobs()
{
	bool UpdatedAny = false;

	ThreadSystem()->EnterCriticalSection(UpdateJobStatusLock);

	EntityUpdateJobsLeft = 0;

	for ( size_t I = 0; I < MAXSI_NUM_ENTITIES; I++)
	{
		if ( EntityList[I] != NULL && EntityList[I]->GetCapabilities() & MAXSI_ENTITY_SUPPORTS_UPDATE )
		{
			ScheduleWorkerThreadJob(EntityUpdateJob, (BYTE*)EntityList[I]);
			EntityUpdateJobsLeft++;
			UpdatedAny = true;
		}
	}

	ThreadSystem()->LeaveCriticalSection(UpdateJobStatusLock);

	if ( UpdatedAny == false ) { return OnSceneUpdated(); }

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError CreatePhysicsUpdateJobs()
{
	bool UpdatedAny = false;
	
	ThreadSystem()->EnterCriticalSection(UpdateJobStatusLock);

	EntityPhysicsJobsLeft = 0;

	for ( size_t I = 0; I < MAXSI_NUM_ENTITIES; I++)
	{
		if ( EntityList[I] != NULL && EntityList[I]->GetCapabilities() & MAXSI_ENTITY_SUPPORTS_PHYSICS )
		{
			ScheduleWorkerThreadJob(EntityPhysicsJob, (BYTE*)EntityList[I]);
			EntityPhysicsJobsLeft++;
			UpdatedAny = true;
		}
	}

	ThreadSystem()->LeaveCriticalSection(UpdateJobStatusLock);

	if ( UpdatedAny == false ) { return OnPhysicsUpdated(); }

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError CreateSceneRenderJobs()
{
#if 1
	MAXSI_TODO("This isn't threaded!");

	OnBegunRenderingScene();

#if 0
	for ( size_t I = 0; I < MAXSI_NUM_ENTITIES; I++)
	{
		if ( EntityList[I] != NULL && (EntityList[I]->GetCapabilities() & MAXSI_ENTITY_SUPPORTS_DRAW) )
		{
			EntityList[I]->Draw();
		}
	}
#endif

	OnFinishedRenderingScene();

#else

	OnRenderingScene();

#endif

	return OnSceneRendered();
}

//=============================================================================
// This is the mainloop!
//=============================================================================

MaxsiError OnNewFrame()
{
	ExitedGameLoop	=	false;

	if ( ShouldMainLoop == false ) { ExitedGameLoop = true; return MAXSI_ERROR_SUCCESS; } 

	//printf("New frame!\n");

	UpdateLogicGameTime();
	CalculateLogicFPS();

	return CreateSceneUpdateJobs();
}

MaxsiError OnSceneUpdated()
{
	//printf("Scene updated!\n");

	return CreatePhysicsUpdateJobs();
}

MaxsiError OnPhysicsUpdated()
{
	//printf("Physics updated!\n");

	ThreadSystem()->SignalEvent(ShouldRenderEvent);

	return OnNewFrame();
}

MaxsiError OnSceneRendered()
{
	// The keyboard must be checked in the same thread that all X stuff goes on it!
	CheckKeyboard();

	////printf("Scene rendered!\n");
	return MAXSI_ERROR_SUCCESS;
}

//=============================================================================
//	These are the jobs in the main loop!
//=============================================================================

MaxsiError EntityUpdateJob(size_t ThreadId, BYTE* Parameter)
{
	((MaxsiEntity*)Parameter)->Update();

	bool WasLastEntity = false;

	ThreadSystem()->EnterCriticalSection(UpdateJobStatusLock);

	EntityUpdateJobsLeft--;

	//////printf("Thread %zu: %zu Entity Updates left!\n", ThreadId, EntityUpdateJobsLeft);

	WasLastEntity = ( EntityUpdateJobsLeft == 0 );

	ThreadSystem()->LeaveCriticalSection(UpdateJobStatusLock);

	if ( WasLastEntity ) { OnSceneUpdated(); }

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError EntityPhysicsJob(size_t ThreadId, BYTE* Parameter)
{
	//sleep(1);

	((MaxsiEntity*)Parameter)->SimulatePhysics();

	bool WasLastEntity = false;

	ThreadSystem()->EnterCriticalSection(UpdateJobStatusLock);

	EntityPhysicsJobsLeft--;

	//printf("Thread %zu: %zu Physics Updates left!\n", ThreadId, EntityPhysicsJobsLeft);

	WasLastEntity = ( EntityPhysicsJobsLeft == 0 );

	ThreadSystem()->LeaveCriticalSection(UpdateJobStatusLock);

	if ( WasLastEntity ) { /*////printf("Thread %zu: Last physics done!\n", ThreadId);*/ OnPhysicsUpdated(); }

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError EntityDrawJob(size_t ThreadId, BYTE* Parameter)
{
	for ( size_t I = 0; I < MAXSI_NUM_ENTITIES; I++)
	{
		if ( EntityList[I] != NULL && EntityList[I]->GetCapabilities() & MAXSI_ENTITY_SUPPORTS_DRAW )
		{
			EntityList[I]->Draw();
		}
	}

	return MAXSI_ERROR_SUCCESS;
}

//=============================================================================
// Functions for updating and getting the logic and render game times!
//=============================================================================

float GetLogicGameTime()
{
	return LogicGameTime;
}

float GetRenderGameTime()
{
	return RenderGameTime;
}

float GetLogicDeltaTime()
{
	return LogicDeltaTime;	
}

float GetRenderDeltaTime()
{
	return RenderDeltaTime;	
}

float GetLogicFPS()
{
	return LogicFPS;
}

float GetRenderFPS()
{
	return RenderFPS;
}

void InitLogicGameTime()
{
	LogicGameTime		=	0.0f;
	LogicLastGameTime	=	0.0f;
	gettimeofday(&LogicBegun, NULL);
}

void InitRenderGameTime()
{
	RenderGameTime		=	0.0f;
	RenderLastGameTime	=	0.0f;
	gettimeofday(&RenderBegun, NULL);
}

void UpdateLogicGameTime()
{
	Time_t Elapsed;
	MaxsiGetSystemTime(&Elapsed);

	LogicLastGameTime = LogicGameTime;
	MaxsiGetSystemTime(&LogicNow);
	SubtractTimes(&Elapsed, &LogicNow, &LogicBegun);
	LogicGameTime = TimeToDouble(Elapsed);
	//LogicGameTime = (float)(LogicNow.tv_sec-LogicBegun.tv_sec) + 0.000001*((float)(LogicNow.tv_usec-LogicBegun.tv_usec));
	LogicDeltaTime = LogicGameTime - LogicLastGameTime;
}

void UpdateRenderGameTime()
{
	Time_t Elapsed;
	MaxsiGetSystemTime(&Elapsed);

	RenderLastGameTime = RenderGameTime;
	MaxsiGetSystemTime(&RenderNow);
	SubtractTimes(&Elapsed, &RenderNow, &RenderBegun);
	RenderGameTime = TimeToDouble(Elapsed);
	//RenderGameTime = (float)(RenderNow.tv_sec-RenderBegun.tv_sec) + 0.000001*((float)(RenderNow.tv_usec-RenderBegun.tv_usec));
	RenderDeltaTime = RenderGameTime - RenderLastGameTime;
}

void CalculateLogicFPS()
{
	LogicFrameNum++;

	if( LogicFrameNum % LogicFramesPerFPS == 0 )
	{
		LogicFPS = ((float)(LogicFramesPerFPS)) / (LogicGameTime-LogicLastFPSGameTime);
		LogicLastFPSGameTime = LogicGameTime;
	}
}

void CalculateRenderFPS()
{
	RenderFrameNum++;

	if( RenderFrameNum % RenderFramesPerFPS == 0 )
	{
		RenderFPS = ((float)(RenderFramesPerFPS)) / (RenderGameTime-RenderLastFPSGameTime);
		RenderLastFPSGameTime = RenderGameTime;
	}
}

size_t GetLogicFrameNum()
{
	return LogicFrameNum;
}

size_t GetRenderFrameNum()
{
	return RenderFrameNum;
}

EndMaxsiNamespace

